package pl.squeaky.mvc.builder;

import java.util.Map;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.ElementChangedEvent;
import org.eclipse.dltk.core.IElementChangedListener;
import org.eclipse.dltk.core.IMethod;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IModelElementDelta;
import org.eclipse.dltk.core.IModelElementVisitor;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;

import pl.squeaky.mvc.MvcProperties;
import pl.squeaky.mvc.model.IAction;
import pl.squeaky.mvc.model.IController;
import pl.squeaky.mvc.model.IControllers;
import pl.squeaky.mvc.model.IModel;
import pl.squeaky.mvc.model.IModels;
import pl.squeaky.mvc.model.IView;
import pl.squeaky.mvc.model.IViews;
import pl.squeaky.mvc.model.MvcFactory;
import pl.squeaky.mvc.nature.MvcNature;

public class MvcBuilder extends IncrementalProjectBuilder {

	public static final String BUILDER_ID = "pl.squeaky.mvc.builder.MvcBuilder";
	
	class ModelDeltaVisitor {
		private IController controller;
		
		public void accept(IModelElementDelta delta) {
			if(visit(delta)) {
				for(IModelElementDelta child : delta.getAffectedChildren()) {
					accept(child);
				}
			}
		}
		
		public boolean visit(IModelElementDelta delta) {
			System.out.println(delta.getElement().getElementName() + ":" + delta.getElement().getClass().getCanonicalName());
			System.out.println(delta.getKind());
			
			IModelElement element = delta.getElement();
			if(element instanceof ISourceModule) {
				IResource resource;
				try {
					resource = element.getUnderlyingResource();
				} catch (ModelException e) {
					throw new RuntimeException(e);
				}
				
				IProject project = resource.getProject();
				MvcNature nature;
				try {
					if(!project.hasNature(MvcNature.NATURE_ID)) {
						return false;
					}
					nature = (MvcNature)project.getNature(MvcNature.NATURE_ID);
				} catch (CoreException e) {
					throw new RuntimeException(e);
				}
				
				IControllers controllers = nature.getControllers();
				IFolder folder = controllers.getPath();
				
				if(!folder.equals(resource.getParent())) {
					return false;
				}
				
				IController found = null;
				for(IController controller : controllers.getCollection()) {
					if(controller.getFile().equals(resource)) {
						found = controller;
						break;
					}
				}
				
				if(found == null) {
					return false;
				}
				
				this.controller = found;
				
				return true;
			} else if(element instanceof IType) {
				IType type = (IType)element;
				if(!isValidController(type)) {
					return false;
				}
				
				if(!type.getElementName().equals(controller.getClassName())) {
					return false;
				}
				
				return true;
			} else if(element instanceof IMethod) {
				IMethod method = (IMethod)element;
				if(!isValidAction(method)) {
					return false;
				}
				
				String name = method.getElementName().substring(0, method.getElementName().length() - "Action".length());
				
				if(delta.getKind() == IModelElementDelta.ADDED) {
					IAction action = MvcFactory.eINSTANCE.createIAction();
					action.setName(name);
					action.setMethod(method);
					
					controller.getActions().add(action);
					
					return false;
				} else if(delta.getKind() == IModelElementDelta.REMOVED) {
					IAction found = null;
					for(IAction action : controller.getActions()) {
						if(action.getName().equals(name)) {
							found = action;
							break;
						}
					}
					
					if(found == null) {
						return false;
					}
					
					controller.getActions().remove(found);
					
					return false;
				}
				
				return false;
			}
			
			return false;
		}
	}
	public MvcBuilder() {
		System.out.println("MvcBuilder::construct");
		DLTKCore.addElementChangedListener(new IElementChangedListener() {
			@Override
			public void elementChanged(ElementChangedEvent event) {
				System.out.println("DLTK changed.");
				new ModelDeltaVisitor().accept(event.getDelta());
			}
		});
	}
	
	@Override
	protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
			throws CoreException {
		monitor.beginTask("MVC Build", IProgressMonitor.UNKNOWN);
		//TODO: remove
		System.out.println("MvcBuilder::build");
		
		if(kind == IncrementalProjectBuilder.FULL_BUILD) {
			fullBuild(monitor);
		} else {
			IResourceDelta delta = getDelta(getProject());
			if(delta == null) {
				fullBuild(monitor);
			} else {
				incremetalBuild(delta, monitor);
			}
		}
		
		monitor.done();
		
		return null;
	}
	
	private void incremetalBuild(IResourceDelta delta, IProgressMonitor monitor) {
		// TODO: remove
		System.out.println("MvcBuilder::incrementalBuild");
		
		MvcNature nature;
		try {
			nature = (MvcNature)getProject().getNature(MvcNature.NATURE_ID);
		} catch(CoreException e) {
			throw new RuntimeException(e);
		}
		
		final IControllers controllers = nature.getControllers();	
		if(controllers != null) {
			final IFolder folder = controllers.getPath();
			IResourceDelta controllersDelta = delta.findMember(folder.getProjectRelativePath());
			if(controllersDelta != null) {
				try {
					controllersDelta.accept(new IResourceDeltaVisitor() {
						@Override
						public boolean visit(IResourceDelta delta)
								throws CoreException {
							IResource resource = delta.getResource();
							if(resource.equals(folder)) {
								return true;
							} else if(resource instanceof IFile) {
								IFile file = (IFile)resource;
								if(!file.getName().endsWith("Controller.php")) {
									return false;
								}
								
								switch(delta.getKind()) {
								case IResourceDelta.ADDED:
								{
									IController controller = MvcFactory.eINSTANCE.createIController();
									controller.setFile(file);
									String name = file.getName();
									name = name.substring(0, name.length() - "Controller.php".length());
									controller.setName(name);
									
									//TODO: remove
									System.out.println("Name: " + name);
									
									controllers.getCollection().add(controller);
									
									return false;
								}
								case IResourceDelta.REMOVED:
									IController found = null;
									for(IController controller : controllers.getCollection()) {
										if(controller.getFile().equals(resource)) {
											found = controller;
											break;
										}
									}
									
									if(found != null) {
										controllers.getCollection().remove(found);
									}
									
									return false;
								}
							}
							return false;
						}
					});
				} catch (CoreException e) {
					throw new RuntimeException(e);
				}
			} else {
				System.out.println("Nothing to be done in controllers.");
			}	
		}
		
		final IModels models = nature.getModels();
		if(models != null) {
			IFolder folder = models.getPath();
			
			if(folder != null) {
				IResourceDelta modelsDelta = delta.findMember(folder.getProjectRelativePath());
				if(modelsDelta != null) {
					try {
						modelsDelta.accept(new IResourceDeltaVisitor() {
							@Override
							public boolean visit(IResourceDelta delta) throws CoreException {
								IResource resource = delta.getResource();
								if(resource instanceof IFolder) {
									return true;
								} else if(isValidModel(resource)) {
									IFile file = (IFile)resource;
									
									if(delta.getKind() == IResourceDelta.ADDED) {
										String name = file.getName().substring(0, file.getName().length() - ".php".length());
										
										IModel model = MvcFactory.eINSTANCE.createIModel();
										model.setFile(file);
										model.setName(name);
										
										models.getCollection().add(model);										
									} else if(delta.getKind() == IResourceDelta.REMOVED) {
										IModel found = null;
										for(IModel model : models.getCollection()) {
											if(model.getFile().equals(file)) {
												found = model;
												break;
											}
										}
										
										if(found != null) {
											models.getCollection().remove(found);
										}
									}
								}
								
								return false;
							}
						});
					} catch (CoreException e) {
						throw new RuntimeException(e);
					}
				}
			}
		}
		
		final IViews views = nature.getViews();
		if(views != null) {
			IFolder folder = views.getPath();
			if(folder != null) {
				final IFolder scriptsFolder = folder.getFolder("scripts");
				if(scriptsFolder.exists()) {
					IResourceDelta viewsDelta = delta.findMember(scriptsFolder.getProjectRelativePath());
					if(viewsDelta != null) {
						try {
							viewsDelta.accept(new IResourceDeltaVisitor() {
								@Override
								public boolean visit(IResourceDelta delta) throws CoreException {
									IResource resource = delta.getResource();
									if(resource instanceof IFolder) {
										return true;
									} else if(isValidView(resource)) {
										IFile file = (IFile)resource;
										if(delta.getKind() == IResourceDelta.ADDED) {
											IContainer parentFolder = file.getParent();
											String name = file.getName().substring(0, file.getName().length() - ".phtml".length());
											if(!parentFolder.equals(scriptsFolder)) {
												name = parentFolder.getName() + "/" + name;
											}
											
											IView view = MvcFactory.eINSTANCE.createIView();
											view.setFile(file);
											view.setName(name);
											
											views.getCollection().add(view);
										} else if(delta.getKind() == IResourceDelta.REMOVED) {
											IView found = null;
											for(IView view : views.getCollection()) {
												if(view.getFile().equals(file)) {
													found = view;
													break;
												}
											}
											
											if(found != null) {
												views.getCollection().remove(found);
											}
										}
									}
									
									return false;
								}
							});
						} catch (CoreException e) {
							throw new RuntimeException(e);
						}
					}
				}
			}
		}
		
		
	}
	
	private void fullBuild(IProgressMonitor monitor) {
		// TODO: remove
		System.out.println("MvcBuilder::fullBuild");
		
		MvcNature nature;
		try {
			nature = (MvcNature)getProject().getNature(MvcNature.NATURE_ID);
		} catch(CoreException e) {
			throw new RuntimeException(e);
		}
		
		final IControllers controllers = MvcFactory.eINSTANCE.createIControllers();
		nature.setControllers(controllers);
		final IFolder controllerFolder;
		try {
			controllerFolder = getProject().getFolder(getProject().getPersistentProperty(MvcProperties.controllerPath));
		} catch (CoreException e) {
			throw new RuntimeException(e);
		}
		controllers.setPath(controllerFolder);
		
		//TODO: remove
		System.out.println("Controllers: " + controllers.getPath().getFullPath().toString());
			
		try {
			controllerFolder.accept(new IResourceVisitor() {
				@Override
				public boolean visit(IResource resource) throws CoreException {
					//TODO: remove
					System.out.println("Visit: " + resource.getFullPath().toString());
					
					if(resource.equals(controllerFolder)) {
						return true;
					} else if(resource instanceof IFile) {
						final IFile file = (IFile)resource;
						if(!isValidController(file)) {
							return false;
						}
						
						final String controllerName = file.getName().substring(0, file.getName().length() - "Controller.php".length());
						
						final IController controller = MvcFactory.eINSTANCE.createIController();
						controller.setFile(file);
						controller.setName(controllerName);
						
						ISourceModule module = DLTKCore.createSourceModuleFrom(file);
						module.accept(new IModelElementVisitor() {
							@Override
							public boolean visit(IModelElement element) {
								if(element instanceof ISourceModule) {
									return true;
								} else if(element instanceof IType) {
									IType type = (IType)element;
									if(!isValidController(type)) {
										return false;
									}
									if(!type.getElementName().equals(controller.getClassName())) {
										return false;
									}
																		
									System.out.println("Name: " + controllerName);
									controllers.getCollection().add(controller);
									
									return true;
								} else if(element instanceof IMethod) {
									IMethod method = (IMethod)element;
									if(!isValidAction(method)) {
										return false;
									}
									
									String actionName = method.getElementName().substring(0, element.getElementName().length() - "Action".length());
									
									IAction action = MvcFactory.eINSTANCE.createIAction();
									action.setName(actionName);
									action.setMethod(method);
									
									controller.getActions().add(action);
									
									return false;
								}
								
								return false;
							}
						});
				
						return false;
					}
					
					return false;
				}
			});
		} catch (CoreException e) {
			throw new RuntimeException(e);
		}

		//TODO: remove
		//System.out.println("Models: " + models.getPath().getFullPath().toString());
		final IModels models = MvcFactory.eINSTANCE.createIModels();
		String modelFolderName;
		try {
			modelFolderName = getProject().getPersistentProperty(MvcProperties.modelPath);
		} catch (CoreException e) {
			throw new RuntimeException(e);
		}
		if(modelFolderName != null) {
			final IFolder modelFolder;
		    modelFolder = getProject().getFolder(modelFolderName);
			models.setPath(modelFolder);
			
			try {
				modelFolder.accept(new IResourceVisitor() {
					@Override
					public boolean visit(IResource resource) throws CoreException {
						if(resource instanceof IFolder) {
							return true;
						} else if(isValidModel(resource)) {
							IFile file = (IFile)resource;
							String name = file.getName().substring(0, file.getName().length() - ".php".length());
							
							IModel model = MvcFactory.eINSTANCE.createIModel();
							model.setFile(file);
							model.setName(name);
							
							models.getCollection().add(model);
						}
						
						return false;
					}
				});
			} catch (CoreException e) {
				throw new RuntimeException(e);
			}
		}
		nature.setModels(models);

		//TODO: remove
		//System.out.println("Views: " + views.getPath().getFullPath().toString());
		final IViews views = MvcFactory.eINSTANCE.createIViews();
		String viewsFolderName;
		try {
			viewsFolderName = getProject().getPersistentProperty(MvcProperties.viewPath);
		} catch(CoreException e) {
			throw new RuntimeException(e);
		}
		if(viewsFolderName != null) {
			IFolder viewsFolder;
			viewsFolder = getProject().getFolder(viewsFolderName);
			views.setPath(viewsFolder);
			
			final IFolder scriptsFolder = viewsFolder.getFolder("scripts");
			if(scriptsFolder.exists()) {
				try {
					scriptsFolder.accept(new IResourceVisitor() {
						@Override
						public boolean visit(IResource resource) throws CoreException {
							if(resource instanceof IFolder) {
								return true;
							} else if(isValidView(resource)) {
								IFile file = (IFile)resource;
								IContainer parentFolder = file.getParent();
								String name = file.getName().substring(0, file.getName().length() - ".phtml".length());
								if(!parentFolder.equals(scriptsFolder)) {
									name = parentFolder.getName() + "/" + name;
								}
			
								IView view = MvcFactory.eINSTANCE.createIView();
								view.setFile(file);
								view.setName(name);
								
								views.getCollection().add(view);
							}
							return false;
						}
					});
				} catch (CoreException e) {
					throw new RuntimeException(e);
				}
			}
		}
		nature.setViews(views);
	}

	@Override
	protected void startupOnInitialize() {
		System.out.println("MvcBuilder::startup");
	}
	
	@Override
	protected void clean(IProgressMonitor monitor) throws CoreException {
		System.out.println("MvcBuilder::clean");
	}
	
	private boolean isValidController(IFile file) {
		String name = file.getName();
		
		if(!name.endsWith("Controller.php")
		   || name.equals("Controller.php")
		   || Character.isLowerCase(name.charAt(0))) {
			return false;
		} else {
			return true;
		}
	}
	
	private boolean isValidController(IType type) {
		String name = type.getElementName();
		
		if(!name.endsWith("Controller")
		   || name.equals("Controller")
		   || Character.isLowerCase(name.charAt(0))) {
			return false;
		} else {
			return true;
		}
	}
	
	private boolean isValidAction(IMethod method) {
		String name = method.getElementName();
		
		if(!name.endsWith("Action")
		   || name.equals("Action")
		   || Character.isUpperCase(name.charAt(0))) {
			return false;
		} else {
			return true;
		}
	}
	
	private boolean isValidView(IResource resource) {
		String name = resource.getName();
		
		if(!(resource instanceof IFile)
		  || !name.endsWith(".phtml")) {
			return false;
		} else {
			return true;
		}
	}
	
	private boolean isValidModel(IResource resource) {
		String name = resource.getName();
		
		if(!name.endsWith(".php")) {
			return false;
		} else {
			return true;
		}
	}

}
